package com.glodon.paas.account.security.spring.login;

import com.glodon.paas.account.security.oauth1.provider.SimpleOAuthProvider;
import com.glodon.paas.account.security.openid.service.OpenIdService;
import com.glodon.paas.account.security.spring.LoginUser;
import com.glodon.paas.account.web.listener.AuthenticatedSessionServerListener;
import net.oauth.OAuthAccessor;
import net.oauth.OAuthException;
import org.apache.commons.lang.StringUtils;
import org.jasig.cas.CentralAuthenticationService;
import org.jasig.cas.authentication.principal.Service;
import org.jasig.cas.authentication.principal.SimpleWebApplicationServiceImpl;
import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;
import org.jasig.cas.services.RegisteredService;
import org.jasig.cas.services.ServicesManager;
import org.jasig.cas.ticket.TicketException;
import org.jasig.cas.web.support.CookieRetrievingCookieGenerator;
import org.openid4java.message.*;
import org.openid4java.message.ax.FetchResponse;
import org.openid4java.server.ServerManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author Don Li
 */
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationSuccessHandler.class);

    public static final String SERVICE = "service";
    public static final String HYBRID = "hybrid";
    public static final String TOKEN = "oauth_token";
    public static final String IDENTITY = "identity";
    public static final String CLAIMED_ID = "claimedId";

    @Autowired
    private ServerManager manager;

    @Autowired
    private CentralAuthenticationService cas;

    @Autowired
    private CookieRetrievingCookieGenerator tgcGenerator;

    @Autowired
    private ServicesManager servicesManager;

    @Autowired
    private SimpleOAuthProvider oauthProvider;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {

        request.getSession().removeAttribute("loginUrlQueryString");

        String serviceName = request.getParameter(SERVICE);

        boolean hybrid = false;
        if (request.getParameter(HYBRID) != null) {
            hybrid = request.getParameter(HYBRID).equals("on");
        }
        String token = request.getParameter(TOKEN);
        String identityBase = request.getParameter(IDENTITY);
        String claimedIdBase = request.getParameter(CLAIMED_ID);

        if (StringUtils.isBlank(serviceName)) {
            serviceName = "account";
        }

        if (authentication.getPrincipal() instanceof LoginUser) {
        	LoginUser user = (LoginUser) authentication.getPrincipal();

            request.getSession(false).setAttribute(AuthenticatedSessionServerListener.CAS_LOGIN_USER_ID, user.getId());

            LOGGER.debug("Login service is : {}", serviceName);
            Service service = new SimpleWebApplicationServiceImpl(serviceName);

            RegisteredService registeredService = servicesManager.findServiceBy(service);
            if (registeredService == null) {
                LOGGER.error("The login service is unrecongnized, service id is : {}", serviceName);
                throw new ServletException("The login service is unrecongnized");
            } else {
                LOGGER.debug("Recongnized login service, service name is : {}", registeredService.getName());
            }

            String tgtId = createTicketGrantingTicketAndCookie(request, response, user);
            String stId = grantServiceTicket(service, tgtId);

            LOGGER.debug("openid.identity: {}", identityBase + user.getId());
            LOGGER.debug("openid.claimed_id: {}", claimedIdBase + user.getId());

            if ("account".equals(serviceName) || StringUtils.isEmpty(serviceName)) { // login from account
                super.onAuthenticationSuccess(request, response, authentication);
            } else { // relaying party login
                ParameterList requestParams = (ParameterList) request.getSession().getAttribute(OpenIdService.OPENID_REQUEST_PARAM);
                request.getSession().removeAttribute(OpenIdService.OPENID_REQUEST_PARAM);

                if (LOGGER.isDebugEnabled() && requestParams != null) {
                    for (Object o : requestParams.getParameters()) {
                        Parameter parameter = (Parameter) o;
                        LOGGER.debug("Parameter -- {} : {}", parameter.getKey(), parameter.getValue());
                    }
                }

                Message responseMessage = manager.authResponse(requestParams, identityBase + user.getId(), claimedIdBase + user.getId(), true, false);

                fetchAttributes(user, requestParams, responseMessage);

                try {
                    manager.sign((AuthSuccess) responseMessage);
                } catch (Exception e) {
                    LOGGER.error("can't sign response message", e);
                }

                String targetUrl = responseMessage.getDestinationUrl(true) + "&st=" + stId;

                if (hybrid) {
                    request.getSession(false).setAttribute("hybrid-protocol-callback", targetUrl);
                    try {
                        OAuthAccessor accessor = oauthProvider.getAccessor(token);
                        if (Boolean.TRUE.equals(accessor.getProperty("authorized"))) {
                            // returnToConsumer
                        } else {
                            // goto authorization page
                            getRedirectStrategy().sendRedirect(request, response, "/authorize?hybrid=on&oauth_token=" + token);
                        }
                    } catch (OAuthException e) {
                        LOGGER.error("invalid token");
                    }
                } else {
                    LOGGER.debug("redirect user to relaying party callback: {}", targetUrl);
                    getRedirectStrategy().sendRedirect(request, response, targetUrl);
                }
            }
        } else {
            throw new AuthenticationServiceException("Authentication Principal is not an instance of " + LoginUser.class.getCanonicalName());
        }
    }

    private String createTicketGrantingTicketAndCookie(HttpServletRequest request, HttpServletResponse response, LoginUser user) {
        UsernamePasswordCredentials credentials = new UsernamePasswordCredentials();
        credentials.setUsername(user.getId());
        credentials.setPassword(user.getPassword());
        try {
            String tgtId = cas.createTicketGrantingTicket(credentials);
            tgcGenerator.addCookie(request, response, tgtId);
            return tgtId;
        } catch (TicketException e) {
            LOGGER.error("TGT cannot be created", e);
        }
        return null;
    }

    private String grantServiceTicket(Service service, String tgtId) {
        String stId = null;
        try {
            stId = cas.grantServiceTicket(tgtId, service);
        } catch (TicketException e) {
            LOGGER.error("ST cannot be granted to service : {}", service.getId());
        }
        return stId;
    }

    private void fetchAttributes(LoginUser user, ParameterList requestParams, Message responseMessage) {
        if ("fetch_request".equals(requestParams.getParameterValue("openid.ext1.mode"))) {
            String atrribute1 = requestParams.getParameterValue("openid.ext1.type.attr1");
            String atrribute2 = requestParams.getParameterValue("openid.ext1.type.attr2");
            FetchResponse fetchResponse = FetchResponse.createFetchResponse();
            if ("email".equals(atrribute1)) {
                fetchResponse.addAttribute("email", user.getEmail());
            }
            if ("username".equals(atrribute2)) {
                String userName = user.getUsername();
                fetchResponse.addAttribute("username", (userName == null) ? "" : userName);
            }
            try {
                responseMessage.addExtension(fetchResponse);
            } catch (MessageException e) {
                LOGGER.error("can't add attribute fetch to response", e);
            }
        }
    }
}
